
export type TestFun<Value> = (a: Value, b: Value) => any;
export type ArrayCallbackFun<ThisValue, Value, ReturnType> = (this: ThisValue, value: Value, index: number, array: Value[]) => ReturnType;


/**
 * 移动数组的元素，会改变原数组
 * @remarks
 * 将数组中的元素 从 from 移动到 to 
 * @param arr 
 * @param from 
 * @param to 
 * @returns 原数组对象
 */
export function moveItemInArray<Item>(arr: Item[], from: number, to: number): Item[] {
    if (from === to) return arr;
    const [item] = arr.splice(from!, 1);
    arr.splice(to, 0, item);
    return arr;
}

/**
 * 移动数组的元素，不改变原数组
 * @remarks
 * 将数组中的元素 从 from 移动到 to 
 * @param arr 
 * @param from 
 * @param to 
 * @returns 新的数组
 */
export function moveItemInNewArray<Item>(arr: Item[], from: number, to: number): Item[] {
    const fromItem = arr[from];
    const newArr = arr.filter((item, index) => index !== from);
    newArr.splice(to, 0, fromItem);
    return newArr;
}

/**
 * 批量移动数组的元素，会改变原数组
 * @remarks
 * 将数组中的元素 从 from 移动到 to 
 * from 与 to 中的索引一一对应
 * @param arr 
 * @param froms 
 * @param tos 
 * @returns 原数组对象
 */
export function moveItemsInArray<Item>(arr: Item[], froms: number[], tos: number[]): Item[] {
    const fromItems = froms.map(function (oldIndex, index) {
        return { item: arr[oldIndex], to: tos[index] };
    });

    for (const { item } of fromItems) {
        const currIndex = arr.indexOf(item);
        arr.splice(currIndex, 1);
    }

    for (const { item, to } of fromItems) {
        arr.splice(to, 0, item);
    }

    return arr;
}

/**
 * 在新数组中批量移动数组的元素，不会改变原数组
 * @remarks
 * 将数组中的元素 从 from 移动到 to 
 * from 与 to 中的索引一一对应
 * 
 * 该方法会比 moveItemsInArray 性能更高一些
 * @param arr 
 * @param froms 
 * @param tos 
 * @returns 新数组
 */
export function moveItemsInNewArray<Item>(arr: Item[], froms: number[], tos: number[]): Item[] {
    const fromItems = froms.map((from) => arr[from]);
    const newArr = arr.filter((from, index) => !froms.includes(index));
    for (let i = 0; i < froms.length; i++) {
        newArr.splice(tos[i], 0, fromItems[i]);
    }
    return newArr;
}







/**
 * 将可迭代的对象转成 JSON 数组片断
 * @param items 
 * @param space 
 * @returns 
 */
export function toJSONArrayFragment(items: Iterable<any>, space?: string | number) {
    const separator = "/";
    let spaceSize = typeof space === "string" ? space.length : (space ?? 0);
    const startOffset = Math.min(spaceSize, 1);
    const arr = [separator, ...items, separator];
    const json = JSON.stringify(arr, null, space);
    const start = json.indexOf(separator) + 3 + startOffset;
    const end = json.lastIndexOf(separator) - 1 - spaceSize;
    return json.slice(start, end);
}



/**
 * 将 项目转成 JSON 的数组中的 item 片断
 * @param item 
 * @param space 
 * @param itemSeparator - 两个 item 之间的分隔符；默认情况下，如果 space 有值且不为0，则 itemSeparator 默认为 \n 否则 为 空
 * @returns 
 */
export function toJSONItemFragment(item: any, space?: string | number, itemSeparator?: string) {
    let fragment = JSON.stringify(item, null, space) + ",";
    if (space) itemSeparator ??= "\n";
    if (itemSeparator) fragment += itemSeparator;
    return fragment;
}



















//安全操作：开始

/*
JavaScript 数组中与遍历相关的方法都是非安全 和 非严谨的，当在遍历时增删原始数组的元素时，会引出问题；
详情请参考文章：https://www.jianshu.com/p/6dd641d0c13d

以下 安全操作 的方法便是用来解决这些问题的；
 */





/**
 * array_safelyOperateIndexs(indexList, operation, thisValue)
 * 安全操作指定的索引
 * @param indexList : [Index]   需要被操作的索引数组
 * @param operation : (currentValue,currentIndex,currentArray)=>Void     执行的操作
 * @param thisValue ? : any   可选，默认值是被操作的数组，即调用者；操作 operation 的 this 值
 * @returns [Item]   被操作的元素列表
 *
 *
 * operation(currentValue,currentIndex,currentArray)=>Void
 * @param currentValue : any   调用 operation 时的元素的值；
 * @param currentIndex : number     调用 operation 时 currentValue 对应的最新状态的索引值；
 * @param currentArray : Array   调用 operation 时 被操作时最新状态的数组；
 *
 */
export function array_safelyOperateIndexs<T, ThisValue = T[]>(array: T[], indexList: number[], operation: ArrayCallbackFun<ThisValue, T, void>, thisValue?: ThisValue): T[] {

    if (thisValue == undefined) {
        // @ts-ignore
        thisValue = array;
    }

    let itemList = array.filter(function (currentValue, index) {
        return indexList.includes(index);
    });


    itemList.forEach((currentValue) => {
        let currentIndex = array.indexOf(currentValue);
        // @ts-ignore
        operation.call(thisValue, currentValue, currentIndex, array);
    });

    return itemList;
}













/**
 * array_safelyOperateItems(itemList, operation, thisValue)
 * 安全操作指定的元素
 * @param itemList : [Item]   需要被操作的元素的数组
 * @param operation : (currentValue,currentIndex,currentArray)=>Void     执行的操作
 * @param thisValue ? : any   可选，默认值是被操作的数组，即调用者；操作 operation 的 this 值
 * @returns [Index]   被操作的元素的索引的列表；
 *
 *
 * operation(currentValue,currentIndex,currentArray)=>Void
 * @param currentValue : any   调用 operation 时的元素的值；
 * @param currentIndex : number     调用 operation 时 currentValue 对应的最新状态的索引值；
 * @param currentArray : Array   调用 operation 时 被操作时最新状态的数组；
 *
 */
export function array_safelyOperateItems<T, ThisValue = T[]>(array: T[], itemList: T[], operation: ArrayCallbackFun<ThisValue, T, void>, thisValue?: ThisValue): number[] {

    if (thisValue == undefined) {
        // @ts-ignore
        thisValue = array;
    }


    let itemListCopy: T[] = [];
    let indexList = itemList.map((item) => {
        itemListCopy.push(item);
        return array.indexOf(item);
    });

    itemListCopy.forEach((currentValue) => {
        let currentIndex = array.indexOf(currentValue);
        // @ts-ignore
        operation.call(thisValue, currentValue, currentIndex, array);
    });

    return indexList;

}











/**
 * array_safelyFilter(operation, thisValue)
 * 安全地操作并过滤所有元素；与 forEach 和 filter 的区别是： safelyFilter 能保证会遍历数组中所有已存在的元素，不会受 operation 中的行为的影响；
 * @param operation : (currentValue,currentIndex,currentArray)=>boolean | undefined     执行的操作， 该函数的返回值表示是否要过滤出该元素
 * @param thisValue ? : any   可选，默认值是被操作的数组，即调用者；操作 operation 的 this 值
 * @returns [Item]  返回被 operation 过滤出的元素
 *
 *
 * operation(currentValue,currentIndex,currentArray)=>boolean | undefined
 * @param currentValue : any   调用 operation 时的元素的值；
 * @param currentIndex : number     调用 operation 时 currentValue 对应的最新状态的索引值；
 * @param currentArray : Array   调用 operation 时 被操作时最新状态的数组；
 * @returns boolean | undefined  表示是否要过滤出 currentValue ；
 *
 */
export function array_safelyFilter<T, ThisValue = T[]>(array: T[], operation: ArrayCallbackFun<ThisValue, T, any>, thisValue?: ThisValue): T[] {

    if (thisValue == undefined) {
        // @ts-ignore
        thisValue = array;
    }

    const arrayCopy = array.slice();

    return arrayCopy.filter((currentValue) => {
        const currentIndex = array.indexOf(currentValue);
        // @ts-ignore
        return operation.call(thisValue, currentValue, currentIndex, array);
    });

}


//安全操作：结束









//移动元素：开始



/**
 * move(fromIndex, toIndex, length = 1)
 * 根据索引移动数组的元素
 * @param fromIndex : number   被移动的元素的开始索引
 * @param toIndex : number    元素被移动到的新位置的索引
 * @param length ? : number   可选，默认值：1 ； 被移动的元素的长度；
 * @returns Array   被移动的元素的数组
 */
export function array_move<T>(array: T[], fromIndex: number, toIndex: number, length = 1): T[] {
    let moveEleArr = array.splice(fromIndex, length);
    array.splice(toIndex, 0, ...moveEleArr);
    return moveEleArr;
}



/**
 * moveTo(toIndex, ...fromIndexs)
 * 根据索引批量移动数组的元素
 * @param toIndex : number    元素被移动到的新位置的索引
 * @param fromIndex : number   被移动的元素的索引
 * @returns Array<Item>   被移动的元素的数组
 */
export function array_moveTo<T>(array: T[], toIndex: number, ...fromIndexs: number[]): T[] {

    let moveItems = array_deleteIndexs(array, ...fromIndexs);
    array.splice(toIndex, 0, ...moveItems);

    return moveItems;
}




/**
 * moveItemsTo(toIndex, ...items)
 * 批量移动数组的指定元素
 * @param toIndex : number    元素被移动到的新位置的索引
 * @param item : any   被移动的元素
 * @returns Array<number>   被移动的元素的索引的数组
 */
export function array_moveItemsTo<T>(array: T[], toIndex: number, ...items: T[]): number[] {

    let indexArr = array_deleteItems(array, ...items);
    array.splice(toIndex, 0, ...items);
    return indexArr;
}


/**
 * moveToUseTest(toIndex, needMoveTest)
 * 根据测试函数批量移动数组的元素
 * @param toIndex : number    元素被移动到的新位置的索引
 * @param needMoveTest : (currentValue,index,arr)=>boolean    测试数组元素是否需要被移动的函数，返回 boolean 值，表示当前元素 currentValue 是否需要被移动；
 * @returns Array<Item>   被移动的元素的数组
 */
export function array_moveToUseTest<T, ThisValue = T[]>(array: T[], toIndex: number, needMoveTest: ArrayCallbackFun<ThisValue, T, any>, thisValue?: ThisValue): T[] {

    let moveItems = array_deleteUseTest(array, needMoveTest, thisValue);
    array.splice(toIndex, 0, ...moveItems);

    return moveItems;
}


//移动元素：结束







//插入元素：开始


/**
 * insertItem(item, toIndex = 0, equalTest)
 * 将指定元素插入到调用者数组中指定索引处，并且会删除调用者数组中与 item 相同的元素
 * @param item : any    被插入的元素
 * @param toIndex : number    元素被插入到的位置的索引
 * @param equalTest ? : (a,b)=>boolean    可选， 默认是通过全等 === 来判断元素是否相等的；测试数组元素是否相同的函数，返回 boolean 值，表示 元素a 和 元素b  相同；
 * @returns Array<Item>   被删除的元素
 */
export function array_insertItem<T>(array: T[], item: T, toIndex = 0, equalTest?: TestFun<T>): T[] {

    if (!equalTest) {
        equalTest = function (a, b) {
            return a === b;
        };
    }


    let deleItems = array.filter((currentValue) => {
        return equalTest.call(array, currentValue, item);
    });


    array_deleteItems(array, ...deleItems);
    array.splice(toIndex, 0, item);

    return deleItems;

}


/**
 * insertItemList(itemList, toIndex = 0, equalTest)
 * 将指定数组itemList中的元素插入到调用者数组的指定索引处，并且会删除调用者数组中 与 itemList中元素 相同的元素
 * @param itemList : [any]    被插入的元素数组
 * @param toIndex ? : number    可靠；默认值：0 ； 元素被插入到的位置的索引；
 * @param equalTest ? : (a,b)=>boolean    可选， 默认是通过全等 === 来判断元素是否相等的；测试数组元素是否相同的函数，返回 boolean 值，表示 元素a 和 元素b  相同；
 * @returns Array<Item>   被删除的元素
 */
export function array_insertItemList<T>(array: T[], itemList: T[], toIndex?: number, equalTest?: TestFun<T>): T[] {
    return itemList.reduceRight((deleItemList, item) => {
        let deleItems = array_insertItem(array, item, toIndex, equalTest);
        deleItemList.unshift(...deleItems);
        return deleItemList;
    }, [] as T[]);
}

//插入元素：结束






//删除元素：开始

/**
 * delete(start, end)
 * 根据索引删除数组的元素
 * @param start : number   被删除的元素的开始索引
 * @param end ? : number   可选，默认值：start ； 被删除的元素的结束索引；
 * @returns Array   被删除的元素的数组
 */
/**
 * delete(start, end)
 * 根据索引删除数组的元素
 * @param start : number   被删除的元素的开始索引
 * @param end ? : number   可选，默认值：start ； 被删除的元素的结束索引；
 * @returns Array   被删除的元素的数组
 */
export function array_delete<T>(array: T[], start: number, end?: number): T[] {
    if (end == null) {
        end = start;
    }
    let length = end + 1 - start;
    return array.splice(start, length);
}







/**
 * deleteIndexs(...indexs)
 * 删除数组中指定的元素
 * @param index : number   被删除的元素的索引
 * @returns Array<Item>   被删除的元素的数组
 */
export function array_deleteIndexs<T>(array: T[], ...indexs: number[]): T[] {
    return array_safelyOperateIndexs(array, indexs, function (currentValue, currentIndex, currentArray) {
        currentArray.splice(currentIndex, 1);
    }, array);

}






/**
 * deleteItems(...items)
 * 删除数组中指定的元素
 * @param item : any   被删除的元素
 * @returns Array<number>   被删除的元素的索引数组
 */
export function array_deleteItems<T>(array: T[], ...items: T[]): number[] {

    return array_safelyOperateItems(array, items, function (currentValue, currentIndex, currentArray) {
        currentArray.splice(currentIndex, 1);
    }, array);

}







/**
 * deleteUseTest(needDeleteTest)
 * 根据测试函数批量删除数组的元素
 * @param needDeleteTest : (currentValue,index,arr)=>boolean    测试数组元素是否需要被删除的函数，返回 boolean 值，表示当前元素 currentValue 是否需要被删除；
 * @returns Array<Item>   被删除的元素的数组
 */
export function array_deleteUseTest<T, ThisValue = T[]>(array: T[], needDeleteTest: ArrayCallbackFun<ThisValue, T, any>, thisValue?: ThisValue): T[] {

    if (thisValue === undefined) {
        // @ts-ignore
        thisValue = array;
    }

    let itemList = array.filter((currentValue, currentIndex, arr) => {
        // @ts-ignore
        return needDeleteTest.call(thisValue, currentValue, currentIndex, arr);
    });


    array_safelyOperateItems(array, itemList, function (currentValue, currentIndex, currentArray) {
        currentArray.splice(currentIndex, 1);
    }, array);


    return itemList;
}


//删除元素：结束




//查找元素：开始

/**
 * filterIndexs(filterTest,thisArg)
 * 该方法创建一个新的数组，新数组中的元素是通过检查指定数组中符合条件的所有元素的索引。
 * @param filterTest : (currentValue,index,arr)=>boolean    用来测试数组的每个元素的函数。调用时使用参数 (currentValue,index,arr)。返回true表示保留该元素（通过测试），false则不保留
 * @param thisArg ? : any 可选。执行 callback 时的用于 this 的值。
 * @returns Array<Index>   通过测试的元素的索引
 */
export function array_filterIndexs<T, ThisValue = T[]>(array: T[], filterTest: ArrayCallbackFun<ThisValue, T, any>, thisValue?: ThisValue): number[] {

    if (thisValue == undefined) {
        // @ts-ignore
        thisValue = array;
    }

    return array.reduce(function (indexList, currentValue, index, arr) {
        // @ts-ignore
        if (filterTest.call(thisValue, currentValue, index, arr)) {
            indexList.push(index);
        }
        return indexList;
    }, [] as number[]);

}





//查找元素：结束








/**
 * 比较两个数组是否相等
 * @remarks
 * 两个数组相等的充要条件是：
 * 元素个数相等 且 元素顺序相等
 * @param arr1 
 * @param arr2 
 * @param equalTest 
 * @returns 
 */
export function array_isEqual<T>(arr1: T[], arr2: T[], equalTest?: TestFun<T>): boolean {
    if (arr1.length !== arr2.length) return false;

    if (!equalTest) {
        equalTest = function (a, b) {
            return a === b;
        };
    }
    
    return  arr1.every((item1,index)=>equalTest(item1,arr2[index]));
}




/**
 * 按指定顺序对数组进行排序
 * @param item 待排序的数组
 * @param order 排序顺序数组
 * @returns 排序后的数组
 */
export function array_sortByOrder<Item>(item:Item[],order:Item[]){
    return item.sort((a,b) => {
      let aIndex = order.indexOf(a);
      let bIndex = order.indexOf(b);
      if (aIndex === -1) aIndex = Number.MAX_SAFE_INTEGER;
      if (bIndex === -1) bIndex = Number.MAX_SAFE_INTEGER;
      const value = aIndex - bIndex;
      if (value !== 0) return value;
      return a < b ? -1 : 1;
    });
}
